package org.enhance.common.helper;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import java.util.Map;

/**
 * <p>
 * bean Definition 注册助手，用于在启动过程中动态的通过 BeanDefinition 向容器里注入bean
 * </p>
 *
 * @author wenpan 2022/11/13 21:36
 */
@Slf4j
public class BeanDefinitionRegisterHelper {

    private BeanDefinitionRegisterHelper() {
    }

    /**
     * <p>
     * 按照指定的 [beanName + bean类型 + 指定构造函数 + 自定义bean属性值] 注册bean定义信息到容器，
     * 如果 bean Definition 已存在于容器，则覆盖它
     * </p>
     *
     * @param registry        registry
     * @param beanName        beanName
     * @param override        如果bean定义信息存在是否允许覆盖
     * @param clazz           bean的Class
     * @param scope           bean的作用域 see org.springframework.beans.factory.config.BeanDefinition#SCOPE_SINGLETON
     * @param properties      bean的属性值
     * @param constructorArgs bean的构造函数值
     * @author wenpan 2022/11/13 9:43 下午
     */
    public static void register(BeanDefinitionRegistry registry,
                                String beanName,
                                Class<?> clazz,
                                boolean override,
                                String scope,
                                Map<String, Object> properties,
                                Object... constructorArgs) {
        // 如果 bean Definition 已存在于容器，则通过 override 参数控制是否要覆盖它
        if (ifPresent(registry, beanName, clazz) || registry.containsBeanDefinition(beanName)) {
            if (override) {
                log.warn("bean definition already exists, overrides it, beanName: {}", beanName);
                registry.removeBeanDefinition(beanName);
            } else {
                throw new RuntimeException(String.format("[%s] already exists, not allow to override.", beanName));
            }
        }
        doRegister(registry, beanName, clazz, scope, properties, constructorArgs);
    }

    /**
     * <p>
     * 按照指定的 [beanName + bean类型 + 指定构造函数 + 自定义bean属性值] 注册单例bean定义信息到容器，
     * 如果 bean Definition 已存在于容器，则覆盖它
     * </p>
     *
     * @param registry        registry
     * @param beanName        beanName
     * @param override        如果bean定义信息存在是否允许覆盖
     * @param clazz           bean的Class
     * @param properties      bean的属性值
     * @param constructorArgs bean的构造函数值
     * @author wenpan 2022/11/13 9:43 下午
     */
    public static void register(BeanDefinitionRegistry registry,
                                String beanName,
                                Class<?> clazz,
                                boolean override,
                                Map<String, Object> properties,
                                Object... constructorArgs) {
        register(registry, beanName, clazz, override, BeanDefinition.SCOPE_SINGLETON, properties, constructorArgs);
    }

    /**
     * <p>
     * 按照指定的 [beanName + bean类型 + 指定构造函数 + 自定义bean属性值] 注册单例bean定义信息到容器，
     * 如果 bean Definition 已存在于容器，则覆盖它
     * </p>
     *
     * @param registry        registry
     * @param beanName        beanName
     * @param clazz           bean的Class
     * @param properties      bean的属性值
     * @param constructorArgs bean的构造函数值
     * @author wenpan 2022/11/13 9:43 下午
     */
    public static void registerIfAbsent(BeanDefinitionRegistry registry,
                                        String beanName,
                                        Class<?> clazz,
                                        Map<String, Object> properties,
                                        Object... constructorArgs) {
        // 如果bean定义信息已存在于容器，则默认覆盖他
        register(registry, beanName, clazz, true, properties, constructorArgs);
    }

    /**
     * <p>
     * 按照指定的 [beanName + bean类型 + 指定构造函数 + 自定义bean属性值] 注册bean定义信息到容器
     * </p>
     *
     * @param registry        registry
     * @param beanName        beanName
     * @param clazz           beanType
     * @param properties      bean属性值集合
     * @param constructorArgs bean构造函数参数数组
     * @author wenpan 2022/11/20 10:39 下午
     */
    private static void doRegister(BeanDefinitionRegistry registry,
                                   String beanName,
                                   Class<?> clazz,
                                   String scope,
                                   Map<String, Object> properties,
                                   Object[] constructorArgs) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        // 添加构造函数参数值
        for (Object constructorArg : constructorArgs) {
            builder.addConstructorArgValue(constructorArg);
        }
        // 填充bean属性值
        if (MapUtils.isNotEmpty(properties)) {
            properties.forEach(builder::addPropertyValue);
        }
        // 单例注入
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        beanDefinition.setScope(scope);
        // 注册到 beanDefinitionMap
        registry.registerBeanDefinition(beanName, beanDefinition);
    }

    /**
     * <p>
     * 按bean名称 + bean类型查询某类型的bean定义信息是否存在于容器
     * </p>
     *
     * @param registry registry
     * @param beanName beanName
     * @param clazz    beanType
     * @return boolean
     * @author wenpan 2022/11/20 10:36 下午
     */
    public static boolean ifPresent(BeanDefinitionRegistry registry, String beanName, Class<?> clazz) {
        String[] beanNames = getBeanNames((ListableBeanFactory) registry, clazz);
        return ArrayUtils.contains(beanNames, beanName);
    }

    /**
     * <p>
     * 从beanFactory中获取某类型的bean的名称
     * </p>
     *
     * @param beanFactory beanFactory
     * @param clazz       beanType
     * @return java.lang.String[] beanNames数组
     * @author wenpan 2022/11/20 10:37 下午
     */
    public static String[] getBeanNames(ListableBeanFactory beanFactory, Class<?> clazz) {
        // 包含非单例的bean，但不允许提前创建（不存在时）
        return beanFactory.getBeanNamesForType(clazz, true, false);
    }

}